home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection Student Program / ADC Tools Sampler CD Disk 3 1999.iso / Metrowerks CodeWarrior / Java Support / Java_Source / IFC_112 / netscape / util / Unarchiver.java < prev    next >
Encoding:
Text File  |  1999-05-28  |  19.3 KB  |  580 lines  |  [TEXT/CWIE]

  1. // Unarchiver.java
  2. // By Ned Etcode
  3. // Copyright 1995, 1996, 1997 Netscape Communications Corp.  All rights reserved.
  4.  
  5. package netscape.util;
  6.  
  7. import java.io.*;
  8. import netscape.application.Application;
  9.  
  10. /** Object subclass implementing the Decoder interface to decode a graph of
  11.   * objects from an Archive. The following example demonstrates how to use
  12.   * an Unarchiver to retrieve the first root object from an Archive read from
  13.   * System.in:
  14.   *
  15.   * <pre>
  16.   *    archive = new Archive();
  17.   *    archive.readASCII(System.in)
  18.   *    rootIdentifiers = archive.rootIdentifiers();
  19.   *    unarchiver = new Unarchiver(archive);
  20.   *    rootObject = unarchiver.unarchiveIdentifier(rootIdentifiers[0]);
  21.   * </pre>
  22.   *
  23.   * @see Decoder
  24.   * @see Archive
  25.   * @see Archiver
  26.   * @note 1.1 newInstance to protected
  27.   * @note 1.1.1 use now Application.classForName if application available
  28.   */
  29. public class Unarchiver implements Decoder {
  30.     Archive archive;
  31.     ArchivingStack stack = new ArchivingStack();
  32.  
  33.     Object objectForId[];
  34.     boolean referenceGivenOut[];
  35.  
  36.     int unarchivedCount;
  37.     Object unarchivedObjects[];
  38.     ExternalCoder unarchivedCoders[];
  39.  
  40.     Object currentObject;
  41.     ClassTable currentTable;
  42.     int currentId;
  43.     int currentColumnCount;
  44.     int currentRow;
  45.     int currentColumn;
  46.  
  47.     netscape.application.Application application;
  48.     boolean applicationInitialized = false;
  49.  
  50.     /** Primitive constructor creating an Unarchiver that retrieves objects
  51.       * from <b>archive</b>. Do not mutate the Archive while using the
  52.       * Unarchiver.
  53.       */
  54.     public Unarchiver(Archive archive) {
  55.         super();
  56.  
  57.         this.archive = archive;
  58.     }
  59.  
  60.     /** Returns the archive from which the Unarchiver decodes objects.
  61.       */
  62.     public Archive archive() {
  63.         return archive;
  64.     }
  65.  
  66.     /** A convenience method for reading an object from a stream.
  67.       * Equivilent to the code:
  68.       * <pre>
  69.       *     archive = new Archive();
  70.       *     archive.read(in);
  71.       *     unarchiver = new Unarchiver(archive);
  72.       *
  73.       *     rootIds = archive.rootIdentifiers();
  74.       *     if (rootIds == null || rootIds.length == 0)
  75.       *         return null;
  76.       *
  77.       *     return unarchiver.unarchiveIdentifier(rootIds[0]);
  78.       * </pre>
  79.       */
  80.     public static Object readObject(InputStream inputStream) throws
  81.                                                             IOException,
  82.         CodingException {
  83.         Archive archive;
  84.         Unarchiver unarchiver;
  85.         int rootIds[];
  86.  
  87.         archive = new Archive();
  88.         archive.read(inputStream);
  89.         unarchiver = new Unarchiver(archive);
  90.  
  91.         rootIds = archive.rootIdentifiers();
  92.         if (rootIds == null || rootIds.length == 0)
  93.             return null;
  94.  
  95.         return unarchiver.unarchiveIdentifier(rootIds[0]);
  96.     }
  97.  
  98.     /** Unarchives a graph of objects starting from the object identified by
  99.       * <b>identifier</b> in an archive. That object, and all objects it
  100.       * references, will be reconstructed by calling their empty constructor
  101.       * followed by <b>decode()</b>.
  102.       * @see Archive#rootIdentifiers
  103.       * @see Codable
  104.       * @see Decoder
  105.       */
  106.     public Object unarchiveIdentifier(int identifier) throws CodingException {
  107.         Object rootObject;
  108.  
  109.         if (identifier == 0)
  110.             return null;
  111.  
  112.         // We make these arrays big enough to map all the ids in the archive.
  113.  
  114.         if (objectForId == null) {
  115.             referenceGivenOut = new boolean[archive.identifierCount()];
  116.             objectForId = new Object[referenceGivenOut.length];
  117.             unarchivedObjects = new Object[referenceGivenOut.length];
  118.             unarchivedCoders = new ExternalCoder[referenceGivenOut.length];
  119.         }
  120.  
  121.         // Make sure that this is empty.  It will be filled up by
  122.         // objectForId() as it unarchives objects.
  123.  
  124.         clearFinishList();
  125.  
  126.         try {
  127.             // Pull out the root object.  This will cause all the objects it
  128.             // references, and the ones they reference, etc.  to be
  129.             // unarchived.
  130.  
  131.             rootObject = objectForIdentifier(identifier);
  132.  
  133.             // Make a pass over all the newly unarchived objects and give them
  134.             // a chance to do some post unarchiving clean up now that their
  135.             // all their brethren have been created.
  136.  
  137.             processFinishList();
  138.         } finally {
  139.             // No matter what happens, be sure we clean up after ourselves.
  140.  
  141.             clearFinishList();
  142.         }
  143.  
  144.         return rootObject;
  145.     }
  146.  
  147.     // ALERT!  This is the only dependency on application in util.  There
  148.     // should be a general solution to the Class.forName() problem with null
  149.     // ClassLoaders.
  150.  
  151.     /** Called to look up a class from the Archive by name.
  152.       */
  153.     protected Class classForName(String className) throws CodingException {
  154.         Class cls = null;
  155.  
  156.         if(!applicationInitialized) {
  157.             application = Application.application();
  158.             applicationInitialized = true;
  159.         }
  160.  
  161.         try {
  162.             if (application != null)
  163.                 cls = application.classForName(className);
  164.             else
  165.                 cls = Class.forName(className);
  166.         } catch (ClassNotFoundException e) {
  167.             creationException(e.toString(), className);
  168.         } catch (NoSuchMethodError e) {
  169.             creationException(e.toString(), className);
  170.         }
  171.  
  172.         return cls;
  173.     }
  174.  
  175.     /** @private */
  176.     protected Object newInstance(String className) throws CodingException {
  177.         Class archivedClass;
  178.         Object object = null;
  179.  
  180.         archivedClass = classForName(className);
  181.  
  182.         try {
  183.             object = archivedClass.newInstance();
  184.         } catch (InstantiationException e) {
  185.             creationException(e.toString(), className);
  186.         } catch (IllegalAccessException e) {
  187.             creationException(e.toString(), className);
  188.         } catch (NoSuchMethodError e) {
  189.             creationException(e.toString(), className);
  190.         }
  191.  
  192.         return object;
  193.     }
  194.  
  195.     /** Whenever we have a problem making an object from the Archive, we
  196.       * try to throw a helpful message.
  197.       */
  198.     private void creationException(String baseException, String className)
  199.         throws CodingException {
  200.         throw new CodingException(baseException + ".  Class " + className +
  201.             " must be public and define a constructor taking no arguments.");
  202.     }
  203.  
  204.     // All ids > 0 map to non-null objects.  id = 0 maps to null.
  205.  
  206.     private Object objectForIdentifier(int id) throws CodingException {
  207.         ExternalCoder coder;
  208.         Object object;
  209.         ClassTable table;
  210.         String className;
  211.         ClassInfo info;
  212.  
  213.         // This works and won't go off the end of the array because we made
  214.         // the objectForId array big enough to hold all the objects in the
  215.         // archive.
  216.  
  217.         object = objectForId[id];
  218.         if (object != null) {
  219.             // If the object is not null, then we have given a reference out
  220.             // to someone.  We keep this state to detect replacement cycles.
  221.  
  222.             referenceGivenOut[id] = true;
  223.             return object;
  224.         } else if (id == 0)
  225.             return null;
  226.  
  227.         // We haven't seen this id before, so we are going to have to make a
  228.         // new instance.
  229.  
  230.         table = archive.classTableForIdentifier(id);
  231.         className = table.className();
  232.  
  233.         // We always check to see if there is an external coder for a given
  234.         // class name.
  235.  
  236.         coder = archive.externalCoderForName(className);
  237.         if (coder != null) {
  238.             object = coder.newInstance(className);
  239.         } else {
  240.             object = newInstance(className);
  241.         }
  242.  
  243.         // To make unarchiving go fast, we stuff the ClassTable's fieldNames
  244.         // with the exact Strings the class is using so that pointer
  245.         // comparisons can be used to early-out equality tests.
  246.  
  247.         if (!table.hasUniqueStrings()) {
  248.             info = new ClassInfo(className);
  249.  
  250.             if (coder != null)
  251.                 coder.describeClassInfo(object, info);
  252.             else
  253.                 ((Codable)object).describeClassInfo(info);
  254.  
  255.             table.uniqueStrings(info);
  256.         }
  257.  
  258.         // Put the original object in the list to get finishDecoding() later.
  259.         // If the object was replaced, the new object should not get
  260.         // finishDecoding() since it was not unarchived.  The original object
  261.         // can always forward finishDecoding() if appropriate.
  262.  
  263.         addToFinishList(coder, object);
  264.  
  265.         // This is where we decode the newly created object.  Be careful to
  266.         // read the object back out of the table in case it was replaced
  267.         // during the call to decode().
  268.  
  269.         objectForId[id] = object;
  270.         pushUnarchivingState(object, id);
  271.  
  272.         if (coder != null)
  273.             coder.decode(object, this);
  274.         else
  275.             ((Codable)object).decode(this);
  276.  
  277.         popUnarchivingState();
  278.         return objectForId[id];
  279.     }
  280.  
  281.     /** This method pushes any current unarchiving state onto the stack and
  282.       * sets up to begin unarchiving the given object. The pushed state
  283.       * will be restored in popUnarchivingState().
  284.       */
  285.     private void pushUnarchivingState(Object object, int id) {
  286.         stack.pushUnarchiver(this);
  287.  
  288.         currentObject = object;
  289.         currentTable = archive.classTableForIdentifier(id);
  290.         currentId = id;
  291.         currentRow = archive.rowForIdentifier(id);
  292.         currentColumn = -1;
  293.         currentColumnCount = currentTable.fieldCount;
  294.     }
  295.  
  296.     /** This method pops the stack of archiving states. The current object is
  297.       * picked up where we left off.
  298.       */
  299.     private void popUnarchivingState() {
  300.         stack.popUnarchiver(this);
  301.     }
  302.  
  303.     /** This maintains the list of objects which have been unarchived in
  304.       * this session.
  305.       */
  306.     private void addToFinishList(ExternalCoder coder, Object object) {
  307.         unarchivedCoders[unarchivedCount] = coder;
  308.         unarchivedObjects[unarchivedCount] = object;
  309.         unarchivedCount++;
  310.     }
  311.  
  312.     private void processFinishList() throws CodingException {
  313.         int i, count;
  314.         ExternalCoder coder;
  315.         Object object;
  316.  
  317.         count = unarchivedCount;
  318.         for (i = 0; i < count; i++) {
  319.             coder = unarchivedCoders[i];
  320.             object = unarchivedObjects[i];
  321.  
  322.             if (coder != null)
  323.                 coder.finishDecoding(object);
  324.             else
  325.                 ((Codable)object).finishDecoding();
  326.  
  327.             // Clear things out while we're here.  If an exception gets
  328.             // thrown, clearFinishList() will be called anyway.
  329.  
  330.             unarchivedCoders[i] = null;
  331.             unarchivedObjects[i] = null;
  332.         }
  333.  
  334.         unarchivedCount = 0;
  335.     }
  336.  
  337.     private void clearFinishList() {
  338.         int i, count;
  339.  
  340.         count = unarchivedCount;
  341.         for (i = 0; i < count; i++) {
  342.             unarchivedCoders[i] = null;
  343.             unarchivedObjects[i] = null;
  344.         }
  345.  
  346.         unarchivedCount = 0;
  347.     }
  348.  
  349.     /** This method is called at the beginning of each unarchive... method
  350.       * to make sure that currentColumn matches the given key. In
  351.       * general, the keys will be in the same order as the columns so the
  352.       * pointer equality test will succeed and we'll rip right along.
  353.       */
  354.     private void prepareToUnarchiveField(String key) throws CodingException {
  355.         int i, count;
  356.         String fieldNames[];
  357.  
  358.         count = currentColumnCount;
  359.         fieldNames = currentTable.fieldNames;
  360.  
  361.         // Scan forward looking for a matching column.  It is kind of common
  362.         // to omit fields when archiving/unarchiving, so skipping forward a
  363.         // few after a miss will usually get us back on track.
  364.  
  365.         for (i = currentColumn + 1; i < count; i++) {
  366.             if (key == fieldNames[i]) {
  367.                 currentColumn = i;
  368.                 return;
  369.             }
  370.         }
  371.  
  372.         // Our optimism has not paid off.  Go ask the ClassTable for
  373.         // the column.
  374.  
  375.         currentColumn = currentTable.columnForField(key);
  376.         if (currentColumn < 0) {
  377.             throw new CodingException("Unknown field name: " + key);
  378.         }
  379.     }
  380.  
  381.     /** Decoder interface method that returns the version information for the
  382.       * class named <b>className</b>.  Objects can use this information to
  383.       * bring forward old encodings at runtime.
  384.       */
  385.     public int versionForClassName(String className) throws CodingException {
  386.         return currentTable.versionForClassName(className);
  387.     }
  388.  
  389.     /** Decoder interface method that decodes the boolean value associated
  390.       * with the string <b>key</b>.
  391.       */
  392.     public boolean decodeBoolean(String key) throws CodingException {
  393.         prepareToUnarchiveField(key);
  394.         return currentTable.booleanAt(currentRow, currentColumn);
  395.     }
  396.  
  397.     /** Decoder interface method that decodes the boolean array associated
  398.       * with the string <b>key</b>.
  399.       */
  400.     public boolean[] decodeBooleanArray(String key) throws CodingException {
  401.         prepareToUnarchiveField(key);
  402.         return currentTable.booleanArrayAt(currentRow, currentColumn);
  403.     }
  404.  
  405.     /** Decoder interface method that decodes the character value associated
  406.       * with the string <b>key</b>.
  407.       */
  408.     public char decodeChar(String key) throws CodingException {
  409.         prepareToUnarchiveField(key);
  410.         return currentTable.charAt(currentRow, currentColumn);
  411.     }
  412.  
  413.     /** Decoder interface method that decodes the character array associated
  414.       * with the string <b>key</b>.
  415.       */
  416.     public char[] decodeCharArray(String key) throws CodingException {
  417.         prepareToUnarchiveField(key);
  418.         return currentTable.charArrayAt(currentRow, currentColumn);
  419.     }
  420.  
  421.     /** Decoder interface method that decodes the byte value associated with
  422.       * the string <b>key</b>.
  423.       */
  424.     public byte decodeByte(String key) throws CodingException {
  425.         prepareToUnarchiveField(key);
  426.         return currentTable.byteAt(currentRow, currentColumn);
  427.     }
  428.  
  429.     /** Decoder interface method that decodes the byte array associated with
  430.       * the string <b>key</b>.
  431.       */
  432.     public byte[] decodeByteArray(String key) throws CodingException {
  433.         prepareToUnarchiveField(key);
  434.         return currentTable.byteArrayAt(currentRow, currentColumn);
  435.     }
  436.  
  437.     /** Decoder interface method that decodes the short value associated with
  438.       * the string <b>key</b>.
  439.       */
  440.     public short decodeShort(String key) throws CodingException {
  441.         prepareToUnarchiveField(key);
  442.         return currentTable.shortAt(currentRow, currentColumn);
  443.     }
  444.  
  445.     /** Decoder interface method that decodes the short array associated with
  446.       * the string <b>key</b>.
  447.       */
  448.     public short[] decodeShortArray(String key) throws CodingException {
  449.         prepareToUnarchiveField(key);
  450.         return currentTable.shortArrayAt(currentRow, currentColumn);
  451.     }
  452.  
  453.     /** Decoder interface method that decodes the integer value associated
  454.       * with the string <b>key</b>.
  455.       */
  456.     public int decodeInt(String key) throws CodingException {
  457.         prepareToUnarchiveField(key);
  458.         return currentTable.intAt(currentRow, currentColumn);
  459.     }
  460.  
  461.     /** Decoder interface method that decodes the integer array associated
  462.       * with the string <b>key</b>.
  463.       */
  464.     public int[] decodeIntArray(String key) throws CodingException {
  465.         prepareToUnarchiveField(key);
  466.         return currentTable.intArrayAt(currentRow, currentColumn);
  467.     }
  468.  
  469.     /** Decoder interface method that decodes the long value associated with
  470.       * the string <b>key</b>.
  471.       */
  472.     public long decodeLong(String key) throws CodingException {
  473.         prepareToUnarchiveField(key);
  474.         return currentTable.longAt(currentRow, currentColumn);
  475.     }
  476.  
  477.     /** Decoder interface method that decodes the long array value associated
  478.       * with the string <b>key</b>.
  479.       */
  480.     public long[] decodeLongArray(String key) throws CodingException {
  481.         prepareToUnarchiveField(key);
  482.         return currentTable.longArrayAt(currentRow, currentColumn);
  483.     }
  484.  
  485.     /** Decoder interface method that decodes the float value associated with
  486.       * the string <b>key</b>.
  487.       */
  488.     public float decodeFloat(String key) throws CodingException {
  489.         prepareToUnarchiveField(key);
  490.         return currentTable.floatAt(currentRow, currentColumn);
  491.     }
  492.  
  493.     /** Decoder interface method that decodes the float array associated with
  494.       * the string <b>key</b>.
  495.       */
  496.     public float[] decodeFloatArray(String key) throws CodingException {
  497.         prepareToUnarchiveField(key);
  498.         return currentTable.floatArrayAt(currentRow, currentColumn);
  499.     }
  500.  
  501.     /** Decoder interface method that decodes the double value associated with
  502.       * the string <b>key</b>.
  503.       */
  504.     public double decodeDouble(String key) throws CodingException {
  505.         prepareToUnarchiveField(key);
  506.         return currentTable.doubleAt(currentRow, currentColumn);
  507.     }
  508.  
  509.     /** Decoder interface method that decodes the double array associated with
  510.       * the string <b>key</b>.
  511.       */
  512.     public double[] decodeDoubleArray(String key) throws CodingException {
  513.         prepareToUnarchiveField(key);
  514.         return currentTable.doubleArrayAt(currentRow, currentColumn);
  515.     }
  516.  
  517.     /** Decoder interface method that decodes the string value associated with
  518.       * the string <b>key</b>.
  519.       */
  520.     public String decodeString(String key) throws CodingException {
  521.         prepareToUnarchiveField(key);
  522.         return currentTable.stringAt(currentRow, currentColumn);
  523.     }
  524.  
  525.     /** Decoder interface method that decodes the string array associated with
  526.       * the string <b>key</b>.
  527.       */
  528.     public String[] decodeStringArray(String key) throws CodingException {
  529.         prepareToUnarchiveField(key);
  530.         return currentTable.stringArrayAt(currentRow, currentColumn);
  531.     }
  532.  
  533.     /** Decoder interface method that decodes a reference to another Codable
  534.       * object.
  535.       */
  536.     public Object decodeObject(String key) throws CodingException {
  537.         prepareToUnarchiveField(key);
  538.         return objectForIdentifier(currentTable.identifierAt(currentRow,
  539.             currentColumn));
  540.     }
  541.  
  542.     /** Decoder interface method that decodes an array of Codable objects. The
  543.       * references to the Codable objects are shared, but the reference to the
  544.       * array is not.
  545.       */
  546.     public Object[] decodeObjectArray(String key) throws CodingException {
  547.         int i;
  548.         int ids[];
  549.         Object objects[];
  550.  
  551.         prepareToUnarchiveField(key);
  552.         ids = currentTable.identifierArrayAt(currentRow, currentColumn);
  553.         if (ids == null) {
  554.             return null;
  555.         }
  556.  
  557.         objects = new Object[ids.length];
  558.  
  559.         for (i = 0; i < ids.length; i++)
  560.             objects[i] = objectForIdentifier(ids[i]);
  561.  
  562.         return objects;
  563.     }
  564.  
  565.     /** Decoder interface method that replaces references to the object
  566.       * currently being decoded with <b>replacement</b>. This method throws a
  567.       * CodingException when an attempt is made to replace an object which has
  568.       * already been seen by other objects. For maximum safety, this method
  569.       * should only be called from leaves of the object graph.
  570.       */
  571.     public void replaceObject(Object replacement) throws CodingException {
  572.         // Need to print out interesting debugging information here.  ALERT!
  573.         if (referenceGivenOut[currentId]) {
  574.             throw new CodingException("Circular replacement exception");
  575.         }
  576.  
  577.         objectForId[currentId] = replacement;
  578.     }
  579. }
  580.